package com.googlecode.jvmpart.support;

import java.io.File;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.googlecode.jvmpart.ProcessExecution;
import com.googlecode.jvmpart.ProcessManager;
import com.googlecode.jvmpart.proc.JvmProcess;
import com.googlecode.jvmpart.utils.KillProcess;

/**
 * Manager processes status and persistent locally
 * @author chunzhan.he
 *
 */
public class LocalProcessManager implements ProcessManager{
	private static final Log logger = LogFactory.getLog(LocalProcessManager.class);
	private static final String APPL_NAME = "com.googlecode.jvmpart";
	private static final String USER_HOME = System.getProperty("user.home");
	protected static File store = new File(USER_HOME, APPL_NAME);
	public LocalProcessManager() {
		if(!store.exists()) store.mkdir();
	}
	private static final String PPID_FORMAT = "%s-%s";
	public static String formatPPId(String ppid, long timestamp) {
		return String.format(PPID_FORMAT, ppid, Long.toHexString(timestamp));
	}
	public void start(String ppid, long timestamp, String pid, Map<String, String> params) {
		PrintStream out = null;
		try {
			ppid = formatPPId(ppid, timestamp);
			File pfile = new File(store, ppid);		
			if(!pfile.exists()) pfile.mkdir();
			out = newPrintStream(ppid, pid);
			out.println("Parent PID:"+ppid+" Child PID:"+pid);
			out.println(params);
			out.flush();
		} catch (IOException e) {
			logger.error(e, e);
		} finally {
			if(out!=null) out.close();
		}
	}
	public void stop(String ppid, long timestamp, String pid, Map<String, String> params) {
		ppid = formatPPId(ppid, timestamp);
		File pfile = new File(store, ppid);
		File cfile = new File(pfile, pid);
		logger.debug("delete file "+pid+":"+cfile.delete());
	}
	public void exit(String ppid, long timestamp, Set<ProcessExecution> set) {
		int errorCount = errorCount(set);
		if(errorCount==0) {
			ppid = formatPPId(ppid, timestamp);
			File f = new File(store, ppid);			
			if(f.exists())
				logger.debug("delete forder "+ppid+":"+f.delete());
		}
		logger.info("Processed:"+set.size()+" Error:"+errorCount);
	}
	public void error(String ppid, long timestamp, String pid, Throwable e) {
		ppid = formatPPId(ppid, timestamp);
		printStackTrace(ppid, pid, e);
	}
	public void kill(String ppid) {
		String killLog = String.format(KILL_LOG, ppid);
		int exitCode;
		try {
			exitCode = KillProcess.kill(ppid).waitFor();
			checkExitCode(ppid, exitCode);
		} catch (Throwable e) {
			logger.error(e, e);
			printStackTrace(ppid, killLog, e);
		} finally {
			killSubProcesses(ppid, true);
		}
	}
	protected static int errorCount(Set<ProcessExecution> set) {
		Iterator<ProcessExecution> items = set.iterator();
		int errorCount = 0;
		while(items.hasNext()) {
			ProcessExecution item = items.next();
			if(item.getExitCode()!=JvmProcess.EXIT_CODE_NORMAL)
				errorCount++;			
		}
		return errorCount;		
	}
	protected static boolean exitNormal(Set<ProcessExecution> set) {
		return errorCount(set)==0;
	}
	private static final String KILL_LOG = "kill_%s.log";
	protected static void killSubProcesses(String ppid, boolean delete) {
		File f = findLatestFile(ppid);
		if(f==null||!f.exists()) return;
		ppid = f.getName();
		String killLog = String.format(KILL_LOG, ppid);
		int exitCode;
		String[] childPIDs = f.list();
		Process[] processes = new Process[childPIDs.length];
		for(int i=0;i<childPIDs.length;i++) {
			try {
				processes[i] = KillProcess.kill(childPIDs[i]);
			} catch (IOException e) {
				logger.error(e, e);
				printStackTrace(ppid, killLog, e);
			}
		}
		try {
			for(int j=0;j<processes.length;j++) {
				exitCode = processes[j].waitFor();
				checkExitCode(childPIDs[j], exitCode);
				printStackTrace(ppid, childPIDs[j]
						, new Exception("kill "+childPIDs[j]+" exitcode:"+exitCode));
				if(exitCode==0) {
					File log = new File(f, childPIDs[j]);
					if(delete) {
						logger.debug("delete file "+childPIDs[j]+":"+log.delete());
//					} else {
//						logger.info("rename file "+childPIDs[j]+":"
//								+log.renameTo(new File(log, String.format(KILL_LOG, childPIDs[j]))));
					}
				}
			}
		} catch (Throwable e) {
			logger.error(e, e);
			printStackTrace(ppid, killLog, e);
		} finally {
			if(delete) {
				logger.debug("delete forder "+ppid+":"+f.delete());
			}
		}			
	}
	protected static void checkExitCode(String pid, int exitCode) {
		if(exitCode!=0)
			logger.info("kill "+pid+" exitcode:"+exitCode);
		else
			logger.info(pid+" killed!");
	}
	protected static PrintStream newPrintStream(String ppid, String pid) throws IOException{
		PrintStream out = null;
		File pfile = new File(store, ppid);		
		if(!pfile.exists()) pfile.mkdir();
		File log = new File(pfile, pid);
		if(!log.exists()) log.createNewFile();
		out = new PrintStream(new FileOutputStream(log, true));
		return out;
	}
	protected static void printStackTrace(String ppid, String filename, Throwable e) {
		PrintStream out = null;		
		try {
			out = newPrintStream(ppid, filename);
			e.printStackTrace(out);
		} catch (Exception e1) {
			logger.error(e1, e1);
		} finally {
			if(out!=null) out.close();
		}
	}
	protected static void deleteFolder(File folder, boolean subFolders) {
		if(!folder.isDirectory()) throw new IllegalArgumentException("not a forlder");
		File[] files = folder.listFiles();
		for(File file:files) {
			if(file.isFile()) file.delete();
			else {
				if(subFolders) deleteFolder(file, subFolders);
			}
		}
		folder.delete();
	}

	protected static File findLatestFile(final String ppid) {
		File[] files = store.listFiles(new FilenameFilter(){
			public boolean accept(File dir, String name) {
				return name.matches(ppid+"-\\w+");
			}			
		});
		Arrays.sort(files, new Comparator<File>() {
			public int compare(File f1, File f2) {
				return Long.valueOf(f2.lastModified()).compareTo(
						f1.lastModified());
			}
		});
		if (files.length > 0)
			return files[0];
		else
			return null;
	}
}
